/*
 * Copyright 2013-2019 Xia Jun(3979434@qq.com).
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 ***************************************************************************************
 *                                                                                     *
 *                        Website : http://www.farsunset.com                           *
 *                                                                                     *
 ***************************************************************************************
 */
package com.farsunset.cim.sdk.server.slc.handler;

import com.farsunset.cim.sdk.server.coder.AppMessageDecoder;
import com.farsunset.cim.sdk.server.coder.AppMessageEncoder;
import com.farsunset.cim.sdk.server.coder.WebMessageDecoder;
import com.farsunset.cim.sdk.server.coder.WebMessageEncoder;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Sharable
public class SlcCIMNioSocketAcceptor extends CIMNioSocketAcceptor2 {

    protected final boolean loadDeApp;
    protected final boolean loadDeWeb;
    protected final boolean usePlatformOptimization;
    protected final Map<String, ChannelHandler> appChannelHandlerMapByDeBefore;
    protected final Map<String, ChannelHandler> appChannelHandlerMapByDeAfter;
    protected final Map<String, ChannelHandler> webChannelHandlerMapByDeBefore;
    protected final Map<String, ChannelHandler> webChannelHandlerMapByDeAfter;

    public SlcCIMNioSocketAcceptor(SlcBuilder builder) {
        super(builder);
        this.loadDeApp = builder.loadDeApp;
        this.loadDeWeb = builder.loadDeWeb;
        this.usePlatformOptimization = builder.usePlatformOptimization;
        this.appChannelHandlerMapByDeBefore = builder.appChannelHandlerMapByDeBefore;
        this.appChannelHandlerMapByDeAfter = builder.appChannelHandlerMapByDeAfter;
        this.webChannelHandlerMapByDeBefore = builder.webChannelHandlerMapByDeBefore;
        this.webChannelHandlerMapByDeAfter = builder.webChannelHandlerMapByDeAfter;
    }

    @Override
    protected void bindAppPort() {
        createAppEventGroup();
        ServerBootstrap bootstrap = createServerBootstrap(getAppBossGroup(), getAppWorkerGroup());
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {
            @Override
            public void initChannel(SocketChannel ch) {
                for (Map.Entry<String, ChannelHandler> entry : appChannelHandlerMapByDeBefore.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
                if (loadDeApp) {
                    ch.pipeline().addLast(new AppMessageDecoder());
                    ch.pipeline().addLast(new AppMessageEncoder());
                }
                for (Map.Entry<String, ChannelHandler> entry : appChannelHandlerMapByDeAfter.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
                ch.pipeline().addLast(loggingHandler);
                ch.pipeline().addLast(new IdleStateHandler(readIdle.getSeconds(), writeIdle.getSeconds(), 0, TimeUnit.SECONDS));
                ch.pipeline().addLast(SlcCIMNioSocketAcceptor.this);
            }
        });

        ChannelFuture channelFuture = bootstrap.bind(appPort).syncUninterruptibly();
        channelFuture.channel().newSucceededFuture().addListener(future -> {
            String logBanner = "\n\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "*                   App Socket Server started on port {}.                        *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n";
            LOGGER.info(logBanner, appPort);
        });
        channelFuture.channel().closeFuture().addListener(future -> this.destroy(getAppBossGroup(), getAppWorkerGroup()));
    }

    @Override
    protected void bindWebPort() {
        createWebEventGroup();
        ServerBootstrap bootstrap = createServerBootstrap(getWebBossGroup(), getWebWorkerGroup());
        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {

            @Override
            public void initChannel(SocketChannel ch) {
                ch.pipeline().addLast(new HttpServerCodec());
                ch.pipeline().addLast(new ChunkedWriteHandler());
                ch.pipeline().addLast(new HttpObjectAggregator(65536));
                ch.pipeline().addLast(new WebSocketServerProtocolHandler("/", false));
                for (Map.Entry<String, ChannelHandler> entry : webChannelHandlerMapByDeBefore.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
                if (loadDeWeb) {
                    ch.pipeline().addLast(new WebMessageDecoder());
                    ch.pipeline().addLast(new WebMessageEncoder());
                }
                for (Map.Entry<String, ChannelHandler> entry : webChannelHandlerMapByDeAfter.entrySet()) {
                    ch.pipeline().addLast(entry.getKey(), entry.getValue());
                }
                ch.pipeline().addLast(loggingHandler);
                ch.pipeline().addLast(new IdleStateHandler(readIdle.getSeconds(), writeIdle.getSeconds(), 0, TimeUnit.SECONDS));
                ch.pipeline().addLast(SlcCIMNioSocketAcceptor.this);
            }

        });

        ChannelFuture channelFuture = bootstrap.bind(webPort).syncUninterruptibly();
        channelFuture.channel().newSucceededFuture().addListener(future -> {
            String logBanner = "\n\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "*                   Websocket Server started on port {}.                         *\n" +
                    "*                                                                                   *\n" +
                    "*                                                                                   *\n" +
                    "* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *\n";
            LOGGER.info(logBanner, webPort);
        });
        channelFuture.channel().closeFuture().addListener(future -> this.destroy(getWebBossGroup(), getWebWorkerGroup()));
    }

    public static class SlcBuilder extends Builder {
        private boolean loadDeApp = true;
        private boolean loadDeWeb = true;
        private boolean usePlatformOptimization;
        private final LinkedHashMap<String, ChannelHandler> appChannelHandlerMapByDeBefore = new LinkedHashMap<>();
        private final LinkedHashMap<String, ChannelHandler> appChannelHandlerMapByDeAfter = new LinkedHashMap<>();
        private final LinkedHashMap<String, ChannelHandler> webChannelHandlerMapByDeBefore = new LinkedHashMap<>();
        private final LinkedHashMap<String, ChannelHandler> webChannelHandlerMapByDeAfter = new LinkedHashMap<>();

        public SlcBuilder setLoadDeApp(boolean loadDeApp) {
            this.loadDeApp = loadDeApp;
            return this;
        }

        public SlcBuilder setLoadDeWeb(boolean loadDeWeb) {
            this.loadDeWeb = loadDeWeb;
            return this;
        }


        public SlcBuilder setUsePlatformOptimization(boolean usePlatformOptimization) {
            this.usePlatformOptimization = usePlatformOptimization;
            return this;
        }

        public SlcBuilder setAppChannelHandlerMapByDeBefore(LinkedHashMap<String, ChannelHandler> appChannelHandlerMapByDeBefore) {
            this.appChannelHandlerMapByDeBefore.clear();
            this.appChannelHandlerMapByDeBefore.putAll(appChannelHandlerMapByDeBefore);
            return this;
        }

        public SlcBuilder addAppChannelHandlerByDeBefore(String key, ChannelHandler channelHandler) {
            this.appChannelHandlerMapByDeBefore.put(key, channelHandler);
            return this;
        }

        public SlcBuilder setAppChannelHandlerMapByDeAfter(LinkedHashMap<String, ChannelHandler> appChannelHandlerMapByDeAfter) {
            this.appChannelHandlerMapByDeAfter.clear();
            this.appChannelHandlerMapByDeAfter.putAll(appChannelHandlerMapByDeAfter);
            return this;
        }

        public SlcBuilder addAppChannelHandlerByDeAfter(String key, ChannelHandler channelHandler) {
            this.appChannelHandlerMapByDeAfter.put(key, channelHandler);
            return this;
        }


        public SlcBuilder setWebChannelHandlerMapByDeAfter(LinkedHashMap<String, ChannelHandler> webChannelHandlerMapByDeAfter) {
            this.webChannelHandlerMapByDeAfter.clear();
            this.webChannelHandlerMapByDeAfter.putAll(webChannelHandlerMapByDeAfter);
            return this;
        }

        public SlcBuilder addWebChannelHandlerByDeAfter(String key, ChannelHandler channelHandler) {
            this.webChannelHandlerMapByDeAfter.put(key, channelHandler);
            return this;
        }

        public SlcBuilder setWebChannelHandlerMapByDeBefore(LinkedHashMap<String, ChannelHandler> webChannelHandlerMapByDeBefore) {
            this.webChannelHandlerMapByDeBefore.clear();
            this.webChannelHandlerMapByDeBefore.putAll(webChannelHandlerMapByDeBefore);
            return this;
        }

        public SlcBuilder addWebChannelHandlerByDeBefore(String key, ChannelHandler channelHandler) {
            this.webChannelHandlerMapByDeBefore.put(key, channelHandler);
            return this;
        }

        @Override
        public SlcCIMNioSocketAcceptor build() {
            return new SlcCIMNioSocketAcceptor(this);
        }

    }
}